/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.bingoogolapple.bgabanner.demo.provider;

import cn.bingoogolapple.bgabanner.demo.interfaces.Filterable;
import cn.bingoogolapple.bgabanner.demo.interfaces.WrapperListAdapter;
import cn.bingoogolapple.bgabanner.demo.observe.DataSetObservable;
import ohos.agp.components.*;
import ohos.app.Context;

import java.lang.reflect.Field;
import java.util.ArrayList;

public class GridViewWithHeaderAndFooter extends ListContainer {
    private ItemClickedListener mOnItemClickListener;
    private ItemLongClickedListener mOnItemLongClickListener;
    private int mNumColumns = 3;
    private Component mViewForMeasureRowHeight = null;
    private int mRowHeight = -1;
    private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>();
    private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<FixedViewInfo>();
    private ItemClickHandler mItemClickHandler;

    private static class FixedViewInfo {
        /**
         * view
         */
        public Component view;

        /**
         * viewContainer
         */
        public ComponentContainer viewContainer;

        /**
         * data
         */
        public Object data;

        /**
         * isSelectable
         */
        public boolean isSelectable;
    }

    /**
     * constructor
     *
     * @param context
     */
    public GridViewWithHeaderAndFooter(Context context) {
        super(context);
        initHeaderGridView();
    }

    /**
     * constructor
     *
     * @param context
     * @param attrs
     */
    public GridViewWithHeaderAndFooter(Context context, AttrSet attrs) {
        super(context, attrs);
        initHeaderGridView();
    }

    /**
     * constructor
     *
     * @param context
     * @param attrs
     * @param defStyle
     */
    public GridViewWithHeaderAndFooter(Context context, AttrSet attrs, String defStyle) {
        super(context, attrs, defStyle);
        initHeaderGridView();
    }

    private void initHeaderGridView() {
    }

    /**
     * addHeaderView
     *
     * @param v
     */
    public void addHeaderView(Component v) {
        addHeaderView(v, null, true);
    }

    /**
     * addHeaderView
     *
     * @param v
     * @param data
     * @param isSelectable
     */
    public void addHeaderView(Component v, Object data, boolean isSelectable) {
        BaseItemProvider adapter = getItemProvider();
        if (adapter != null && !(adapter instanceof HeaderViewGridAdapter)) {
            throw new IllegalStateException(
                    "Cannot add header view to grid -- setAdapter has already been called.");
        }
        LayoutConfig lyp = v.getLayoutConfig();

        FixedViewInfo info = new FixedViewInfo();
        StackLayout fl = new StackLayout(getContext());

        if (lyp != null) {
            v.setLayoutConfig(new StackLayout.LayoutConfig(lyp.width, lyp.height));
            fl.setLayoutConfig(new LayoutConfig(lyp.width, lyp.height));
        }
        fl.addComponent(v);
        info.view = v;
        info.viewContainer = fl;
        info.data = data;
        info.isSelectable = isSelectable;
        mHeaderViewInfos.add(info);
        if (adapter != null) {
            ((HeaderViewGridAdapter) adapter).notifyDataSetChanged();
        }
    }

    /**
     * setAdapter
     *
     * @param adapter
     */
    public void setAdapter(BaseItemProvider adapter) {
        if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
            HeaderViewGridAdapter headerViewGridAdapter =
                    new HeaderViewGridAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
            headerViewGridAdapter.setNumColumns(1);
            headerViewGridAdapter.setRowHeight(10000);
            super.setItemProvider(headerViewGridAdapter);
        } else {
            super.setItemProvider(adapter);
        }
    }

    /**
     * addFooterView
     *
     * @param v
     */
    public void addFooterView(Component v) {
        addFooterView(v, null, true);
    }

    /**
     * addFooterView
     *
     * @param v
     * @param data
     * @param isSelectable
     */
    public void addFooterView(Component v, Object data, boolean isSelectable) {
        BaseItemProvider mAdapter = getItemProvider();
        if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) {
            throw new IllegalStateException(
                    "Cannot add header view to grid -- setAdapter has already been called.");
        }

        LayoutConfig lyp = v.getLayoutConfig();

        FixedViewInfo info = new FixedViewInfo();
        StackLayout fl = new StackLayout(getContext());

        if (lyp != null) {
            v.setLayoutConfig(new StackLayout.LayoutConfig(lyp.width, lyp.height));
            fl.setLayoutConfig(new LayoutConfig(lyp.width, lyp.height));
        }
        fl.addComponent(v);
        info.view = v;
        info.viewContainer = fl;
        info.data = data;
        info.isSelectable = isSelectable;
        mFooterViewInfos.add(info);

        if (mAdapter != null) {
            ((HeaderViewGridAdapter) mAdapter).notifyDataSetChanged();
        }
    }

    /**
     * getHeaderViewCount
     *
     * @return header view count
     */
    public int getHeaderViewCount() {
        return mHeaderViewInfos.size();
    }

    /**
     * getFooterViewCount
     *
     * @return footer view count
     */
    public int getFooterViewCount() {
        return mFooterViewInfos.size();
    }

    /**
     * removeHeaderView
     *
     * @param v
     * @return remove header result
     */
    public boolean removeHeaderView(Component v) {
        if (mHeaderViewInfos.size() > 0) {
            boolean result = false;
            BaseItemProvider adapter = getItemProvider();
            if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) {
                result = true;
            }
            removeFixedViewInfo(v, mHeaderViewInfos);
            return result;
        }
        return false;
    }

    /**
     * removeFooterView
     *
     * @param v
     * @return remove footerview result
     */
    public boolean removeFooterView(Component v) {
        if (mFooterViewInfos.size() > 0) {
            boolean result = false;
            BaseItemProvider adapter = getItemProvider();
            if (adapter != null && ((HeaderViewGridAdapter) adapter).removeFooter(v)) {
                result = true;
            }
            removeFixedViewInfo(v, mFooterViewInfos);
            return result;
        }
        return false;
    }

    private void removeFixedViewInfo(Component v, ArrayList<FixedViewInfo> where) {
        int len = where.size();
        for (int i = 0; i < len; ++i) {
            FixedViewInfo info = where.get(i);
            if (info.view == v) {
                where.remove(i);
                break;
            }
        }
    }

    private int getNumColumnsCompatible() {
        try {
            Field numColumns = ListContainer.class.getDeclaredField("mNumColumns");
            numColumns.setAccessible(true);
            return numColumns.getInt(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.fillInStackTrace();
        }
        return mNumColumns;
    }

    private int getColumnWidthCompatible() {
        try {
            Field numColumns = ListContainer.class.getDeclaredField("mColumnWidth");
            numColumns.setAccessible(true);
            return numColumns.getInt(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.fillInStackTrace();
        }
        return 0;
    }

    /**
     * onDetachedFromWindow
     */
    protected void onDetachedFromWindow() {
        mViewForMeasureRowHeight = null;
    }

    /**
     * invalidateRowHeight
     */
    public void invalidateRowHeight() {
        mRowHeight = -1;
    }

    /**
     * getHeaderHeight
     *
     * @param row
     * @return header height value
     */
    public int getHeaderHeight(int row) {
        if (row >= 0) {
            return mHeaderViewInfos.get(row).view.getEstimatedHeight();
        }

        return 0;
    }

    /**
     * getVerticalSpacing
     *
     * @return vertical spacing value
     */
    public int getVerticalSpacing() {
        int value = 0;
        try {
            Field field = ListContainer.class.getDeclaredField("mVerticalSpacing");
            field.setAccessible(true);
            value = field.getInt(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.fillInStackTrace();
        }
        return value;
    }

    /**
     * getHorizontalSpacing
     *
     * @return horizontal spacing value
     */
    public int getHorizontalSpacing() {
        int value = 0;
        try {
            Field field = ListContainer.class.getDeclaredField("mHorizontalSpacing");
            field.setAccessible(true);
            value = field.getInt(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.fillInStackTrace();
        }
        return value;
    }

    /**
     * getRowHeight
     *
     * @return row height value
     */
    public int getRowHeight() {
        if (mRowHeight > 0) {
            return mRowHeight;
        }
        BaseItemProvider adapter = getItemProvider();
        int numColumns = getNumColumnsCompatible();

        // adapter has not been set or has no views in it;
        if (adapter == null || adapter.getCount() <= numColumns * (mHeaderViewInfos.size() + mFooterViewInfos.size())) {
            return -1;
        }
        int mColumnWidth = getColumnWidthCompatible();
        Component view = getItemProvider()
                .getComponent(numColumns * mHeaderViewInfos.size(), mViewForMeasureRowHeight, this);
        LayoutConfig p = (LayoutConfig) view.getLayoutConfig();
        if (p == null) {
            p = new StackLayout.LayoutConfig(-1, -2, 0);
            view.setLayoutConfig(p);
        }

        int childHeightSpec = EstimateSpec
                .getChildSizeWithMode(MeasureSpec.getMeasureSpec(0, MeasureSpec.UNCONSTRAINT), 0, p.height);
        int childWidthSpec = EstimateSpec
                .getChildSizeWithMode(MeasureSpec.getMeasureSpec(mColumnWidth, MeasureSpec.PRECISE), 0, p.width);
        MeasureSpec.getMeasureSpec(childWidthSpec, childHeightSpec);
        mViewForMeasureRowHeight = view;
        mRowHeight = view.getEstimatedHeight();
        return mRowHeight;
    }

    private static class HeaderViewGridAdapter extends WrapperListAdapter implements Filterable {
        private static final ArrayList<FixedViewInfo> EMPTY_INFO_LIST = new ArrayList<FixedViewInfo>();
        private final DataSetObservable mDataSetObservable = new DataSetObservable();
        private final BaseItemProvider mAdapter;
        private ArrayList<FixedViewInfo> mHeaderViewInfos;
        private ArrayList<FixedViewInfo> mFooterViewInfos;
        private int mNumColumns = 1;
        private int mRowHeight = -1;
        private final boolean mIsFilterable;
        private boolean mCachePlaceHoldView = true;
        private boolean mCacheFirstHeaderView = false;

        HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ArrayList<FixedViewInfo> footViewInfos, BaseItemProvider adapter) {
            mAdapter = adapter;
            mIsFilterable = adapter instanceof Filterable;
            if (headerViewInfos == null) {
                mHeaderViewInfos = EMPTY_INFO_LIST;
            } else {
                mHeaderViewInfos = headerViewInfos;
            }
            if (footViewInfos == null) {
                mFooterViewInfos = EMPTY_INFO_LIST;
            } else {
                mFooterViewInfos = footViewInfos;
            }
        }

        /**
         * setNumColumns
         *
         * @param numColumns
         */
        public void setNumColumns(int numColumns) {
            if (numColumns < 1) {
                return;
            }
            if (mNumColumns != numColumns) {
                mNumColumns = numColumns;
                notifyDataSetChanged();
            }
        }

        /**
         * setRowHeight
         *
         * @param height
         */
        public void setRowHeight(int height) {
            mRowHeight = height;
        }

        /**
         * getHeadersCount
         *
         * @return header count
         */
        public int getHeadersCount() {
            return mHeaderViewInfos.size();
        }

        /**
         * getFootersCount
         *
         * @return footer count
         */
        public int getFootersCount() {
            return mFooterViewInfos.size();
        }

        private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {
            if (infos != null) {
                for (FixedViewInfo info : infos) {
                    if (!info.isSelectable) {
                        return false;
                    }
                }
            }
            return true;
        }

        /**
         * removeHeader
         *
         * @param v
         * @return remove header result
         */
        public boolean removeHeader(Component v) {
            for (int i = 0; i < mHeaderViewInfos.size(); i++) {
                FixedViewInfo info = mHeaderViewInfos.get(i);
                if (info.view == v) {
                    mHeaderViewInfos.remove(i);
                    mDataSetObservable.notifyChanged();
                    return true;
                }
            }
            return false;
        }

        /**
         * removeFooter
         *
         * @param v
         * @return remove footer result
         */
        public boolean removeFooter(Component v) {
            for (int i = 0; i < mFooterViewInfos.size(); i++) {
                FixedViewInfo info = mFooterViewInfos.get(i);
                if (info.view == v) {
                    mFooterViewInfos.remove(i);
                    mDataSetObservable.notifyChanged();
                    return true;
                }
            }
            return false;
        }

        /**
         * getCount
         *
         * @return count
         */
        public int getCount() {
            if (mAdapter != null) {
                return (getFootersCount() + getHeadersCount()) * mNumColumns + getAdapterAndPlaceHolderCount();
            } else {
                return (getFootersCount() + getHeadersCount()) * mNumColumns;
            }
        }

        private int getAdapterAndPlaceHolderCount() {
            return (int) (Math.ceil(1f * mAdapter.getCount() / mNumColumns) * mNumColumns);
        }

        /**
         * getItem
         *
         * @param position
         * @return get item by position
         */
        public Object getItem(int position) {
            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
            if (position < numHeadersAndPlaceholders) {
                if (position % mNumColumns == 0) {
                    return mHeaderViewInfos.get(position / mNumColumns).data;
                }
                return null;
            }

            // Adapter
            final int adjPosition = position - numHeadersAndPlaceholders;
            int adapterCount = 0;
            if (mAdapter != null) {
                adapterCount = getAdapterAndPlaceHolderCount();
                if (adjPosition < adapterCount) {
                    if (adjPosition < mAdapter.getCount()) {
                        return mAdapter.getItem(adjPosition);
                    } else {
                        return null;
                    }
                }
            }

            // Footer (off-limits positions will throw an IndexOutOfBoundsException)
            final int footerPosition = adjPosition - adapterCount;
            if (footerPosition % mNumColumns == 0) {
                return mFooterViewInfos.get(footerPosition).data;
            } else {
                return null;
            }
        }

        /**
         * getItemId
         *
         * @param position
         * @return item id
         */
        public long getItemId(int position) {
            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
            if (mAdapter != null && position >= numHeadersAndPlaceholders) {
                int adjPosition = position - numHeadersAndPlaceholders;
                int adapterCount = mAdapter.getCount();
                if (adjPosition < adapterCount) {
                    return mAdapter.getItemId(adjPosition);
                }
            }
            return -1;
        }

        @Override
        public Component getComponent(int position, Component component, ComponentContainer parent) {
            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
            if (position < numHeadersAndPlaceholders) {
                Component headerViewContainer = mHeaderViewInfos
                        .get(position / mNumColumns).viewContainer;
                if (position % mNumColumns == 0) {
                    return headerViewContainer;
                } else {
                    if (component == null) {
                        component = new Component(parent.getContext());
                    }

                    // We need to do this because GridView uses the height of the last item
                    // in a row to determine the height for the entire row.
                    component.setVisibility(Component.INVISIBLE);
                    component.setMinHeight(headerViewContainer.getHeight());
                    return component;
                }
            }

            // Adapter
            final int adjPosition = position - numHeadersAndPlaceholders;
            int adapterCount = 0;
            if (mAdapter != null) {
                adapterCount = getAdapterAndPlaceHolderCount();
                if (adjPosition < adapterCount) {
                    if (adjPosition < mAdapter.getCount()) {
                        return mAdapter.getComponent(adjPosition, component, parent);
                    } else {
                        if (component == null) {
                            component = new Component(parent.getContext());
                        }
                        component.setVisibility(Component.INVISIBLE);
                        component.setMinHeight(mRowHeight);
                        return component;
                    }
                }
            }

            // Footer
            final int footerPosition = adjPosition - adapterCount;
            if (footerPosition < getCount()) {
                Component footViewContainer = mFooterViewInfos
                        .get(footerPosition / mNumColumns).viewContainer;
                if (position % mNumColumns == 0) {
                    return footViewContainer;
                } else {
                    if (component == null) {
                        component = new Component(parent.getContext());
                    }
                    component.setVisibility(Component.INVISIBLE);
                    component.setMinHeight(footViewContainer.getHeight());
                    return component;
                }
            }
            throw new ArrayIndexOutOfBoundsException(position);
        }

        @Override
        public int getItemComponentType(int position) {
            final int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
            final int adapterViewTypeStart = mAdapter == null ? 0 : mAdapter.getComponentTypeCount() - 1;
            int type = -2;
            if (mCachePlaceHoldView) {
                // Header
                if (position < numHeadersAndPlaceholders && position == 0) {
                    if (mCacheFirstHeaderView) {
                        type = adapterViewTypeStart + mHeaderViewInfos.size() + mFooterViewInfos.size() + 1 + 1;
                    }
                } else if (position < numHeadersAndPlaceholders && position % mNumColumns != 0) {
                    type = adapterViewTypeStart + (position / mNumColumns + 1);
                }
            }

            // Adapter
            final int adjPosition = position - numHeadersAndPlaceholders;
            int adapterCount = 0;
            if (mAdapter != null) {
                adapterCount = getAdapterAndPlaceHolderCount();
                if (adjPosition >= 0 && adjPosition < adapterCount) {
                    if (adjPosition < mAdapter.getCount()) {
                        type = mAdapter.getItemComponentType(adjPosition);
                    } else {
                        if (mCachePlaceHoldView) {
                            type = adapterViewTypeStart + mHeaderViewInfos.size() + 1;
                        }
                    }
                }
            }

            if (mCachePlaceHoldView) {
                // Footer
                final int footerPosition = adjPosition - adapterCount;
                if (footerPosition >= 0 && footerPosition < getCount() && (footerPosition % mNumColumns) != 0) {
                    type = adapterViewTypeStart + mHeaderViewInfos.size() + 1 + (footerPosition / mNumColumns + 1);
                }
            }
            return type;
        }

        @Override
        public int getComponentTypeCount() {
            int count = mAdapter == null ? 1 : mAdapter.getComponentTypeCount();
            if (mCachePlaceHoldView) {
                int offset = mHeaderViewInfos.size() + 1 + mFooterViewInfos.size();
                if (mCacheFirstHeaderView) {
                    offset += 1;
                }
                count += offset;
            }
            return count;
        }

        /**
         * getFilter
         *
         * @return TextFilter
         */
        public TextFilter getFilter() {
            if (mIsFilterable) {
                return ((Filterable) mAdapter).getFilter();
            }
            return null;
        }

        /**
         * getWrappedAdapter
         *
         * @return BaseItemProvider
         */
        public BaseItemProvider getWrappedAdapter() {
            return mAdapter;
        }

        /**
         * notifyDataSetChanged
         */
        public void notifyDataSetChanged() {
            mDataSetObservable.notifyChanged();
        }
    }

    @Override
    public void setItemClickedListener(ItemClickedListener listener) {
        mOnItemClickListener = listener;
        super.setItemClickedListener(getItemClickHandler());
    }

    @Override
    public void setItemLongClickedListener(ItemLongClickedListener listener) {
        mOnItemLongClickListener = listener;
        super.setItemLongClickedListener(getItemClickHandler());
    }

    private ItemClickHandler getItemClickHandler() {
        if (mItemClickHandler == null) {
            mItemClickHandler = new ItemClickHandler();
        }
        return mItemClickHandler;
    }

    private class ItemClickHandler implements ItemClickedListener, ItemLongClickedListener {
        @Override
        public void onItemClicked(ListContainer listContainer, Component component, int i, long l) {
            if (mOnItemClickListener != null) {
                int resPos = i - getHeaderViewCount() * getNumColumnsCompatible();
                if (resPos >= 0) {
                    mOnItemClickListener.onItemClicked(listContainer, component, resPos, l);
                }
            }
        }

        @Override
        public boolean onItemLongClicked(ListContainer listContainer, Component component, int i, long l) {
            if (mOnItemLongClickListener != null) {
                int resPos = i - getHeaderViewCount() * getNumColumnsCompatible();
                if (resPos >= 0) {
                    mOnItemLongClickListener.onItemLongClicked(listContainer, component, resPos, l);
                }
            }
            return true;
        }
    }
}
